package es.tid.www.wsdl.parlayx.presence.consumer.v1_1.wsconsumer;

import java.io.StringReader;

import org.apache.axis2.client.Options;
import org.apache.axis2.client.ServiceClient;
import org.apache.axis2.databinding.types.URI;
import org.apache.axis2.transport.http.HttpTransportProperties;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXParseException;

import es.tid.www.wsdl.parlayx.presence.consumer.v1_1.PresenceConsumerServiceStub;
import es.tid.www.wsdl.parlayx.presence.consumer.v1_1.PresenceConsumerServiceStub.PresenceAttributeType;
import es.tid.www.wsdl.parlayx.presence.consumer.v1_1.PresenceConsumerServiceStub.SimpleReference;


/**
 * Presence Consumer ParlayX interface client class. This java class can call remote parlayx webservice
 * to query presence information about a concrete presentity
 */
public class ParlayXPresenceConsumerWSClient {

	
	/**
	 * Gets presence information about a concrete presentity. A watcher is also needed to call remote webservice.
	 * This watcher identity is taken from config properties for all presentities wanted to query.
	 * Depending on the result of the query to webservice, this method will return free, busy or undefined 
	 * state for the audio line. Undefined is taken as free and is so when Presence Server is not able to 
	 * communicate the real state due to a failure in ims communication, an overload or every other reason
	 * @param presentity sip address wanted to be queried about its line state.
	 * @return an integer, which can be 
	 * -1 = if line is busy
	 * 0 = if line state is undefined
	 * 1 = if line is free
	 */
	 public int getUserPresence(String presentity){
	      try{  
	        //create request envelope to be sent to webservice interface
	        es.tid.www.wsdl.parlayx.presence.consumer.v1_1.PresenceConsumerServiceStub.GetUserPresence userPresence = new es.tid.www.wsdl.parlayx.presence.consumer.v1_1.PresenceConsumerServiceStub.GetUserPresence();
	        //read watcher identity from config properties set for the application.
	        //watcher is needed to be subscribed to presentity state.
	        userPresence.setWatcher(new URI(PresenceConsumerConfigInfo.getInstance().getConfigProperty("presence.watcher.identity")));
	        //set presentity to query into request envelope
	        userPresence.setPresentity(new URI(presentity));
	        //set attribute type to PIDF as response format to evaluate
	        es.tid.www.wsdl.parlayx.presence.consumer.v1_1.PresenceConsumerServiceStub.PresenceAttributeType[] attri = new es.tid.www.wsdl.parlayx.presence.consumer.v1_1.PresenceConsumerServiceStub.PresenceAttributeType[1];
	        attri[0] = es.tid.www.wsdl.parlayx.presence.consumer.v1_1.PresenceConsumerServiceStub.PresenceAttributeType.PIDF;
	        userPresence.setAttributes(attri);
	        
	        //create request and call webservice
	        es.tid.www.wsdl.parlayx.presence.consumer.v1_1.PresenceConsumerServiceStub.GetUserPresence0 request = new es.tid.www.wsdl.parlayx.presence.consumer.v1_1.PresenceConsumerServiceStub.GetUserPresence0();
	        request.setGetUserPresence(userPresence);
	        //query about a synchronous end point, because this method must return value when webservice returns a value. 
	        //(opposite to other methods which are called and the ims net response is asked by executing another webservice interface method)
	        String endpointSync=PresenceConsumerConfigInfo.getInstance().getConfigProperty("parlayx.presence.consumer.ws.endpoint.sync");
	        es.tid.www.wsdl.parlayx.presence.consumer.v1_1.PresenceConsumerServiceStub stub = new es.tid.www.wsdl.parlayx.presence.consumer.v1_1.PresenceConsumerServiceStub(endpointSync);
	        
	        //for security reasons, we'll include user and password as basic authentication into soap headers.
	        //we'll read those values from config properties for this application
	        ServiceClient sClient = stub._getServiceClient();
	        Options opt = sClient.getOptions();
	        HttpTransportProperties.Authenticator authenticator = new HttpTransportProperties.Authenticator();
	        authenticator.setRealm(HttpTransportProperties.Authenticator.BASIC);
	        authenticator.setUsername(PresenceConsumerConfigInfo.getInstance().getConfigProperty("security.user"));
	        authenticator.setPassword(PresenceConsumerConfigInfo.getInstance().getConfigProperty("security.password"));
	        authenticator.setPreemptiveAuthentication(true);
	        opt.setProperty(org.apache.axis2.transport.http.HTTPConstants.AUTHENTICATE, authenticator);
	        sClient.setOptions(opt);
	        stub._setServiceClient(sClient);
	        //call webservice through WSDL2Java generated stub and unwrap received response
	        es.tid.www.wsdl.parlayx.presence.consumer.v1_1.PresenceConsumerServiceStub.GetUserPresenceResponse5 response = stub.getUserPresence(request);
	        es.tid.www.wsdl.parlayx.presence.consumer.v1_1.PresenceConsumerServiceStub.GetUserPresenceResponse resp = response.getGetUserPresenceResponse();
	        //obtain the final returned structure. This is an array of PresenceAttribute which we must 
	        //investigate to determinate real line state as PIDF says
	        es.tid.www.wsdl.parlayx.presence.consumer.v1_1.PresenceConsumerServiceStub.PresenceAttribute[] result = resp.getResult();
	        
	        
	        //from result, we'll take attribute for PIDF (if exists)
	        String pidf = null;
	        for(int u=0;u<result.length;u++){
	        	if (
	        		(result[u]!=null)&&
	        		(result[u].getTypeAndValue().getUnionElement().getValue().equals(es.tid.www.wsdl.parlayx.presence.consumer.v1_1.PresenceConsumerServiceStub.PresenceAttributeType._PIDF))
	        		)
	        	{
	                pidf = result[u].getTypeAndValue().getPIDF();
	            }
	        }
	        
	        //if a PIDF is received, search line state into it
	        if (pidf != null){
	
	            try{
	            	//first, we must validate PIDF
                    String JAXP_SCHEMA_LANGUAGE = "http://java.sun.com/xml/jaxp/properties/schemaLanguage";
                    String W3C_XML_SCHEMA = "http://www.w3.org/2001/XMLSchema";
                    String JAXP_SCHEMA_SOURCE = "http://java.sun.com/xml/jaxp/properties/schemaSource";
                    //schema location for validating is set in config properties. Read it
                    String SCHEMA_SOURCE= PresenceConsumerConfigInfo.getInstance().getConfigProperty("urlPIDFSchema");
	  
                    //needed object to parse document
                    javax.xml.parsers.DocumentBuilderFactory factory;
                    javax.xml.parsers.DocumentBuilder builder;
	            
                    //configurate factory
                    factory = javax.xml.parsers.DocumentBuilderFactory.newInstance();
                    factory.setValidating(true);
                    factory.setAttribute(JAXP_SCHEMA_LANGUAGE, W3C_XML_SCHEMA);     
                    factory.setAttribute(JAXP_SCHEMA_SOURCE, SCHEMA_SOURCE);
                    factory.setNamespaceAware(true);    
                    factory.setIgnoringElementContentWhitespace(true);
                    factory.setIgnoringComments(true);
                    //create builder
                    builder = factory.newDocumentBuilder();
                    //create error handler and assign it to builder
                    org.xml.sax.ErrorHandler errorHandler = new org.xml.sax.ErrorHandler() {
                        public void warning(org.xml.sax.SAXParseException e) {
                            e.printStackTrace();
                        }
                        public void error(org.xml.sax.SAXParseException e) throws SAXParseException {
                            throw e;
                        }
                        public void fatalError(org.xml.sax.SAXParseException e) throws SAXParseException{
                            throw e;
                        }
                    };
                    builder.setErrorHandler(errorHandler);
	                //parse document
                    Document presenceInfoDom = builder.parse(new org.xml.sax.InputSource(new StringReader(pidf)));
	 
                    //once we have readed the document, get line state from its informations
                    Node raiz = presenceInfoDom.getDocumentElement();
                    NodeList nivel1 = raiz.getChildNodes();
                    
                    boolean type1 = false;
                    boolean type2 = false;
                    String statusBasic = "";
                    String[] imsSession = null;
                    String note = "";
	                    
                    for (int i=0;i<nivel1.getLength();++i)
                    {
                    	//we are searching for a "tuple" with same "contact" tag as presentity queried.
                    	//depending on found or not, this will be taken as tuple_type1 or tuple_type2
                    	if(nivel1.item(i).getNodeName().contains("tuple"))
                        {
                            NodeList nivel2 = nivel1.item(i).getChildNodes();
                            
                            for (int o=0;o<nivel2.getLength();o++){
                                if(nivel2.item(o).getNodeName().indexOf("contact") > -1){
                                    if(nivel2.item(o).getFirstChild().getNodeValue().equals(presentity.toString())){
                                        type1 = true;
                                    }else{
                                        type2 = true;
                                    }
                                }
                            }
	                            
                            //depending on the type of tuple, we'll search one information or other
                            if (type1){
                            	//if tuple is type1, it's necessary to get status.basic and status.ims-session
                                for (int o=0; o<nivel2.getLength();o++){
                                    if(nivel2.item(o).getNodeName().indexOf("status") > -1){
                                        NodeList nivel3 = nivel2.item(o).getChildNodes();
                                        for(int y=0;y<nivel3.getLength();y++){
                                        	//take basic and ims-session
                                            if(nivel3.item(y).getNodeName().indexOf("basic") > -1){
                                                statusBasic = nivel3.item(y).getFirstChild().getNodeValue();
                                            }else if(nivel3.item(y).getNodeName().contains("ims-session")){
                                                NodeList childsIms = nivel3.item(y).getChildNodes();
                                                imsSession = new String[childsIms.getLength()];
                                                for (int m=0;m<childsIms.getLength();m++){
                                                    String name = childsIms.item(m).getNodeName();
                                                    int ind = name.indexOf(":");
                                                    imsSession[m] = name.substring(ind+1);
                                                }
                                            }
                                        }
                                    }
                                }
                            }else if (type2){
                            	//if tuple is type2, take "note" field
                                for (int o=0; o<nivel2.getLength();o++){
                                    if(nivel2.item(o).getNodeName().indexOf("note") > -1){
                                        note = nivel2.item(o).getFirstChild().getNodeValue();
                                    }
                                }
                            }
                      	}
                    }
	                    
                    
                    //once PIDF is parsed extract conclusions about line state
                    if(type1){
                    	//if type1, check for statusBasic==open and imsSession!=audio for free linestate. 
                    	//Other result will be busy line
                    	if (statusBasic.equals("open")){
                    		if (imsSession != null){   
                    			for(int r=0;r<imsSession.length;r++){
                    				if(imsSession[r].equals("audio")){
                    					return -1;
                    				}
                    			}
                    		}
                    		return 1;
                        }
                    	else{
                    		return -1;
                    	}
                    }
                    else if (type2){
                    	//if type!, check for note==Available for free linestate. 
                    	//Other result will be busy line
                    	if(note.equals("Available")){
                    		return 1;
	                    }
                    	else { return -1; }
	                }else{
	                	//any other result (not extracted type1 or type2 for pidf, for example) will
	                	//be taken as busy line
	                    return -1; 
	                }
	            }catch(Exception e){
	                throw new Exception("Exception in getUserPresence: "+e.toString());
	            }
	            
	        }else{
	            //if doesn't exists pidf, return undefined linestate. This can be done due to a failure
	        	//in Presence Server, for example
	            return 0;
	        }
	      }catch(Exception e){
	    	  //any error occurred during execution will be taken as undefined linestate.
	          return 0;
	      }
	    }
	 
	 /**
	  * This method calls remote webservice for watcher to be subscribed to presentity. This is needed
	  * before receiving notifications, for presence server to know who will received these notifications
	  * @param watcher sip address for the identity to receive presentity state as notification messages
	  * @param presentity sip address for the identity to query presentity state
	  * @throws Exception when some error happens during method execution.
	  */
	 	public void subscribePresence(String watcher, String presentity) throws Exception{
	      try{  

	        //create envelope for the request to send
	        PresenceConsumerServiceStub.SubscribePresence subscribePresence = new PresenceConsumerServiceStub.SubscribePresence();
	        //add method parameters to request envelope
	        subscribePresence.setWatcher(new URI(watcher));
	        URI[] presentities = new URI[1];
	        presentities[0] = new URI(presentity);
	        subscribePresence.setPresentities(presentities);
	        //set a name for the application to use this method.
	        subscribePresence.setApplication("subscription");
	        //create a SimpleReference object. This object will have the reverse endpoint for another
	        //interface where Gateway service will notify every presentity state change.
	        SimpleReference reference = new SimpleReference();
	        //read reverse notification endpoint from config properties and include into SimpleReference object
	        String presenceNotificationEPR=PresenceConsumerConfigInfo.getInstance().getConfigProperty("parlayx.presence.notification.ws.endpoint");
	        reference.setEndpoint(new URI(presenceNotificationEPR));
	        //this correlator is needed for subsequents request to be correlated. All the request in 
	        //same correlator will be associated to the same initial operation. In this method, this 
	        //correlator is not really needed, because a new one is generated when this method is executed.
	        reference.setCorrelator("subcribePresenceCorrelator");
	        //name for the reverse interface. Not only the reverse end point where send notifications is needed
	        //but interface name also to be completely identified.
	        reference.setInterfaceName("PresenceNotificationService");
	        subscribePresence.setReference(reference);
	        
	        //set as presence attribute to manage a PIDF xml content
	        PresenceAttributeType[] attributes = new PresenceAttributeType[1];
	        attributes[0] = PresenceAttributeType.PIDF;
	        subscribePresence.setAttributes(attributes);
	        
	        //create request and add input data
	        PresenceConsumerServiceStub.SubscribePresence3 request = new PresenceConsumerServiceStub.SubscribePresence3();
	        request.setSubscribePresence(subscribePresence);
	        
	        //call webservice through the stub generated by WSDL2Java tool from wsdl.
	        //The endpoint for the webservice to call is readed from config properties
	        String endpointSync=PresenceConsumerConfigInfo.getInstance().getConfigProperty("parlayx.presence.consumer.ws.endpoint.sync");
	        PresenceConsumerServiceStub stub = new PresenceConsumerServiceStub(endpointSync);
	        //include name and password as basic authentication into soap headers, for security reasons
	        ServiceClient sClient = stub._getServiceClient();
	        Options opt = sClient.getOptions();
	        HttpTransportProperties.Authenticator authenticator = new HttpTransportProperties.Authenticator();
	        authenticator.setRealm(HttpTransportProperties.Authenticator.BASIC);
	        authenticator.setUsername(PresenceConsumerConfigInfo.getInstance().getConfigProperty("security.user"));
	        authenticator.setPassword(PresenceConsumerConfigInfo.getInstance().getConfigProperty("security.password"));
	        authenticator.setPreemptiveAuthentication(true);
	        opt.setProperty(org.apache.axis2.transport.http.HTTPConstants.AUTHENTICATE, authenticator);
	        sClient.setOptions(opt);
	        stub._setServiceClient(sClient);
	        
	        //call remote ws method
	        stub.subscribePresence(request);
	        //if nothing extrange happens, don't return nothing. Exception otherwise
	      }catch(Exception e){
	    	  //an exception is thrown when some problem happens while calling remote ws.
	          throw new Exception("An exception happened while calling subscribePresence():"+e.toString());
	      }
	    }
	 
	 /**
	  * Calls remote webservice to finalize notification messages to come related to a presentity.
	  * @param uri sip address of the presentity whose presence state is wanted to be ignored from now
	  * @throws Exception when some problem happens while executing this method
	  */
 	  public void endPresenceNotification(String uri) throws Exception{
 		  try{
 			//create stub object
 		    es.tid.www.wsdl.parlayx.presence.consumer.v1_1.PresenceConsumerServiceStub.EndPresenceNotification endPresence = new es.tid.www.wsdl.parlayx.presence.consumer.v1_1.PresenceConsumerServiceStub.EndPresenceNotification();
 	        //add correlator to operation. The correlator is taken from an object retaining in application 
 		    //which uri is related to which correlator. This relationship is needed for the 
 		    //gateway to work properly and correlate every subsequent request after a subscribePresence
 		    //In other words, gateway service knows active notifications due to the correlator, not due to presentity uri
 	      	endPresence.setCorrelator(LineStateFlags.getInstance().getCorrelator(uri));
 	        //fill request with input data
 	        es.tid.www.wsdl.parlayx.presence.consumer.v1_1.PresenceConsumerServiceStub.EndPresenceNotification8 request = new es.tid.www.wsdl.parlayx.presence.consumer.v1_1.PresenceConsumerServiceStub.EndPresenceNotification8();
 	        request.setEndPresenceNotification(endPresence);
 	        //build the stub WSDL2Java generated object, taking endpoint from config properties
 	        String endpointSync=PresenceConsumerConfigInfo.getInstance().getConfigProperty("parlayx.presence.consumer.ws.endpoint.sync");
 	        es.tid.www.wsdl.parlayx.presence.consumer.v1_1.PresenceConsumerServiceStub stub = new es.tid.www.wsdl.parlayx.presence.consumer.v1_1.PresenceConsumerServiceStub(endpointSync);
 	        //include user and password in basic authentication into soap header, for security reasons.
 	        ServiceClient sClient = stub._getServiceClient();
 	        Options opt = sClient.getOptions();
 	        HttpTransportProperties.Authenticator authenticator = new HttpTransportProperties.Authenticator();
 	        authenticator.setRealm(HttpTransportProperties.Authenticator.BASIC);
 	        authenticator.setUsername(PresenceConsumerConfigInfo.getInstance().getConfigProperty("security.user"));
 	        authenticator.setPassword(PresenceConsumerConfigInfo.getInstance().getConfigProperty("security.password"));
 	        authenticator.setPreemptiveAuthentication(true);
 	        opt.setProperty(org.apache.axis2.transport.http.HTTPConstants.AUTHENTICATE, authenticator);
 	        sClient.setOptions(opt);
 	        stub._setServiceClient(sClient);
 	        //call remote webservice
 	        stub.endPresenceNotification(request);
 	        //if nothing happens, the operation has been successful. An exception is thrown otherwise
 	      }catch(Exception e){
 	    	  //throw an exception if some problem happened during this method execution.
 	          throw new Exception("An exception happened while calling endPresenceNotification():"+e.toString());
 	      }
 	    }
 	  
 	  /**
 	   * Calls remote webservice to start receiving a concrete presentity notifications about its state. 
 	   * The notifications will be received through a reverse interface (see subscribePresence method to
 	   * know how to indicate that reverse interface endpoint)
 	   * @param uri presentity sip address about which receive state notifications through reverse interface
 	   * @throws Exception when some problem happens during this method execution
 	   */
 	  public void startPresenceNotification(String uri) throws Exception{
 	      try{  
 	    	//create request envelope
 	        es.tid.www.wsdl.parlayx.presence.consumer.v1_1.PresenceConsumerServiceStub.StartPresenceNotification startPresence = new es.tid.www.wsdl.parlayx.presence.consumer.v1_1.PresenceConsumerServiceStub.StartPresenceNotification();
 	        //create simple reference object
 	        es.tid.www.wsdl.parlayx.presence.consumer.v1_1.PresenceConsumerServiceStub.SimpleReference reference = new PresenceConsumerServiceStub.SimpleReference();
 	        //add remote interface name to call
 	        reference.setInterfaceName("PresenceConsumer");
 	        //add a correlator for the presentity uri. This correlator is used by gateway service to link 
 	        //subsequent requests
 	        String correlator=LineStateFlags.getInstance().addCorrelatorFor(uri);
 	        reference.setCorrelator(correlator);
 	        //read from config properties reverse notification interface and add it to simple reference.
 	        //That reverse interface will be the endpoint where received the notification messages
 	        URI localEndPoint = new URI(PresenceConsumerConfigInfo.getInstance().getConfigProperty("parlayx.presence.notification.ws.endpoint"));
 	        reference.setEndpoint(localEndPoint);
 	        //add SimpleReference object to request envelope
 	        startPresence.setReference(reference);
 	        //read watcher to subscribe from config properties and add it to request envelope
 	        String watcher=PresenceConsumerConfigInfo.getInstance().getConfigProperty("presence.watcher.identity");
 	        startPresence.setWatcher(new URI(watcher));
 	        //create presentity parameter and add it to request envelope.
 	        URI[] presentities = new URI[1];
 	        presentities[0] = new URI(uri);
 	        startPresence.setPresentities(presentities);
 	        //set checkinmmediate (always true in this call) (see presence OMA specifications)
 	        startPresence.setCheckImmediate(true);
 	        //set frequency parameter as 1000 milliseconds (see presence OMA specifications)
 	        es.tid.www.wsdl.parlayx.presence.consumer.v1_1.PresenceConsumerServiceStub.TimeMetric timeM = new es.tid.www.wsdl.parlayx.presence.consumer.v1_1.PresenceConsumerServiceStub.TimeMetric();
 	        timeM.setUnits(Integer.parseInt("1000"));
 	        timeM.setMetric(es.tid.www.wsdl.parlayx.presence.consumer.v1_1.PresenceConsumerServiceStub.TimeMetrics.Millisecond);
 	        startPresence.setFrequency(timeM);
 	        
 	        //create stub object generated by WSDL2Java tool and fill with request envelope.
 	        //use endpoint readed from config properties
 	        es.tid.www.wsdl.parlayx.presence.consumer.v1_1.PresenceConsumerServiceStub.StartPresenceNotification1 request = new es.tid.www.wsdl.parlayx.presence.consumer.v1_1.PresenceConsumerServiceStub.StartPresenceNotification1();
 	        request.setStartPresenceNotification(startPresence);
 	        String endpointAsync=PresenceConsumerConfigInfo.getInstance().getConfigProperty("parlayx.presence.consumer.ws.endpoint.async");
 	        es.tid.www.wsdl.parlayx.presence.consumer.v1_1.PresenceConsumerServiceStub stub = new es.tid.www.wsdl.parlayx.presence.consumer.v1_1.PresenceConsumerServiceStub(endpointAsync);
 	        //fill an authentication header for soap to include user and password as basic authentication,
 	        //for security considerations
 	        ServiceClient sClient = stub._getServiceClient();
 	        Options opt = sClient.getOptions();
 	        HttpTransportProperties.Authenticator authenticator = new HttpTransportProperties.Authenticator();
 	        authenticator.setRealm(HttpTransportProperties.Authenticator.BASIC);
 	        authenticator.setUsername(PresenceConsumerConfigInfo.getInstance().getConfigProperty("security.user"));
 	        authenticator.setPassword(PresenceConsumerConfigInfo.getInstance().getConfigProperty("security.password"));
 	        authenticator.setPreemptiveAuthentication(true);
 	        opt.setProperty(org.apache.axis2.transport.http.HTTPConstants.AUTHENTICATE, authenticator);
 	        sClient.setOptions(opt);
 	        stub._setServiceClient(sClient);
 	        //call remote webservice
 	        stub.startPresenceNotification(request);
 	        //if nothing extrange happens. The method has been successfully finalized.
 	        //An exception is thrown otherwise
 	      }catch(Exception e){
 	    	  //If some problem happens during method execution, throw an exception
 	          throw new Exception("An exception happened while calling startPresenceNotification():"+e.toString());
 	      }
 	   }
}
